﻿using System;
using System.Collections.Generic;
using System.Linq;
using BO.Management.QueryBuilder;
using CrystalDecisions.Enterprise;

namespace BO.Management
{
    /// <summary>
    /// Base Provider to query the Business Object store
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public abstract class BaseObjectProvider<T> : IDisposable where T : CrystalDecisions.Enterprise.InfoObject
    {

        #region "Private members"
        /// <summary>
        /// Service provider is the internal class to retrieve BO services
        /// </summary>
        private ServiceProvider _serviceProvider;

        /// <summary>
        /// InfoStore is the persistent interface provided by BO to query in its database
        /// </summary>
        private CrystalDecisions.Enterprise.InfoStore _infoStore;

        /// <summary>
        /// A BOE opened session
        /// </summary>
        private CrystalDecisions.Enterprise.EnterpriseSession _session;

        /// <summary>
        /// the info store service from BO' services
        /// </summary>
        private CrystalDecisions.Enterprise.EnterpriseService _service;

        /// <summary>
        /// A class to create BO queries
        /// </summary>
        private InfoStoreQuery _queryBuilder;

        #endregion

        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="logonProvider">An active logonProvider with a opened session</param>
        public BaseObjectProvider(LogonProvider logonProvider)
        {
            if (logonProvider == null || logonProvider.Session == null) throw new ArgumentNullException("BOE Session can't be null");
            _session = logonProvider.Session;

            _serviceProvider = new ServiceProvider(logonProvider);
            _service = _serviceProvider.GetInfoStoreService();

            _infoStore = new CrystalDecisions.Enterprise.InfoStore(_service);


            //Create the query builder for the object kind
            _queryBuilder = new InfoStoreQuery(this.Query_Table);
            if (this.Kind != ObjectKind.Generic && this.Kind != ObjectKind.Instance && this.Kind != ObjectKind.RecuringInstance)
            {
                this.AddFilterToKind();
            }
        }


        public void Dispose()
        {
            if (_infoStore != null) _infoStore.Dispose();
        }

        public CrystalDecisions.Enterprise.InfoStore InfoStore
        {
            get
            {
                return _infoStore;
            }
        }


        /// <summary>
        /// Get the query builder
        /// </summary>
        public InfoStoreQuery QueryBuilder
        {
            get
            {
                return _queryBuilder;
            }
        }

        /// <summary>
        /// Get an object of type by its name according the query parameters. Each found object is treated by the delegate
        /// </summary>
        /// <param name="function"></param>
        /// <returns>Number of found objects with the query</returns>
        public virtual Int32 GetObject(System.Action<T> function)
        {
            using (CrystalDecisions.Enterprise.InfoObjects results = _infoStore.Query(_queryBuilder.GetQuery()))
            {
                if (results != null && results.Count > 0)
                {
                    for (Int32 i = 1; i <= results.Count; i++)
                    {
                        function((T)results[i]);
                    }

                    //We find result so the result of this call is the number of objects
                    return results.Count;
                }
                else
                {
                    //We don't find result so the result of this call is 0
                    return 0;
                }
            }
        }

        /// <summary>
        /// Get an object of type by its name according the query parameters.
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public virtual List<T> GetObject()
        {
            List<T> objects = new List<T>();
            using (CrystalDecisions.Enterprise.InfoObjects results = _infoStore.Query(_queryBuilder.GetQuery()))
            {
                if (results != null && results.Count > 0)
                {
                    for (Int32 i = 1; i <= results.Count; i++)
                    {
                        objects.Add((T)results[i]);
                    }
                }
            }
            return objects;
        }


        /// <summary>
        /// Get an object of type by its name according the query parameters. Each found object is treated by the delegate
        /// </summary>
        /// <param name="function"></param>
        /// <returns>Number of found objects with the query</returns>
        private Int32 GetObject(System.Action<T> function, Int32 parentId, Boolean reccurse, Int32 nbLevels, Int32 levelStart)
        {

            levelStart++;
            Int32 reccurseNbObjects = 0;

            InfoStoreQuery queryBuilder = new InfoStoreQuery(this.Query_Table, this.QueryBuilder.FilterParameters);
            queryBuilder.ChangeParameter<Int32>(InfoStoreFieldNames.ParentId, OperatorType.Equal, parentId);
            if (this.Kind != ObjectKind.Generic && this.Kind != ObjectKind.Instance && this.Kind != ObjectKind.RecuringInstance)
            {
                queryBuilder.AddParameter<String>(InfoStoreFieldNames.Kind, OperatorType.Equal, this.Kind.ToString());
            }

            ////Add filter to instance or not
            //queryBuilder.AddParameter<Int32>(InfoStoreFieldNames.Instance, OperatorType.Equal, 
            //                    (this.Kind == ObjectKind.Instance | this.Kind == ObjectKind.RecuringInstance) ? 1:0 );

            ////Add filter to reccuring instance or not
            //queryBuilder.AddParameter<Int32>(InfoStoreFieldNames.Reccuring, OperatorType.Equal, this.Kind == ObjectKind.RecuringInstance ? 1 : 0);


            using (CrystalDecisions.Enterprise.InfoObjects results = _infoStore.Query(queryBuilder.GetQuery()))
            {
                if (results != null && results.Count > 0)
                {
                    for (Int32 i = 1; i <= results.Count; i++)
                    {
                        function((T)results[i]);

                        //If ssearch type is a folder we can directly do the reccurivity
                        if ((results[i] is CrystalDecisions.Enterprise.Desktop.Folder || results[i].Kind == "Folder") && reccurse && (nbLevels == 0 | nbLevels > levelStart) && this.Kind != ObjectKind.Generic && this.Kind != ObjectKind.Instance)
                        {
                            reccurseNbObjects += this.GetObject(function, results[i].ID, reccurse, nbLevels, levelStart);
                        }

                    }

                    //We find result so the result of this call is the number of objects + number objects found in hierarchical search
                    reccurseNbObjects += results.Count;
                }
            }

            //If search type is not a folder, we have to restart a search only for folders and if reccurse is selected
            if (this.Kind != ObjectKind.Folder && reccurse && (nbLevels == 0 | nbLevels > levelStart))
            {
                InfoStoreQuery queryBuilderContainer = new InfoStoreQuery(this.Query_Table);
                queryBuilderContainer.AddParameter<Int32>(InfoStoreFieldNames.ParentId, OperatorType.Equal, parentId);
                queryBuilderContainer.AddParameter<Int32>(InfoStoreFieldNames.Instance, OperatorType.Equal, 0);

                using (CrystalDecisions.Enterprise.InfoObjects containerResults = _infoStore.Query(queryBuilderContainer.GetQuery()))
                {
                    if (containerResults != null && containerResults.Count > 0)
                    {
                        for (Int32 i = 1; i <= containerResults.Count; i++)
                        {
                            reccurseNbObjects += this.GetObject(function, containerResults[i].ID, reccurse, nbLevels, levelStart);
                        }
                    }
                }/*end search type Folder*/
            }

            return reccurseNbObjects;
        }


        /// <summary>
        /// Get an object by its id
        /// </summary>
        /// <param name="objectId"></param>
        /// <returns></returns>
        public virtual T GetObject(Int32 objectId)
        {

            InfoStoreQuery queryBuilder = new InfoStoreQuery(this.Query_Table);
            queryBuilder.AddParameter<Int32>(InfoStoreFieldNames.Id, OperatorType.Equal, objectId);

            using (CrystalDecisions.Enterprise.InfoObjects results = _infoStore.Query(queryBuilder.GetQuery()))
            {
                if (results != null && results.Count > 0)
                {
                    return (T)results[1];
                }
                else
                {
                    return null;
                }
            }
        }


        /// <summary>
        /// Get objects with reccurse according the number of level to search
        /// </summary>
        /// <param name="ParentId"></param>
        /// <param name="reccurse"></param>
        /// <param name="nbLevels"></param>
        /// <returns></returns>
        public virtual List<T> GetObjectsHierachical(Boolean reccurse, Int32 nbLevels)
        {
            List<T> objects = new List<T>();

            this.GetObjectsHierachical((s) => { objects.Add(s); }, reccurse, nbLevels);
            return objects;
        }

        /// <summary>
        /// Get objects with reccurse according the number of level to search
        /// </summary>
        /// <param name="ParentId"></param>
        /// <param name="reccurse"></param>
        /// <param name="nbLevels"></param>
        /// <returns></returns>
        public virtual Int32 GetObjectsHierachical(System.Action<T> function, Boolean reccurse, Int32 nbLevels)
        {
            //Does the ParentId as been asked
            var filterSearch = from filter in QueryBuilder.FilterParameters
                               where filter.ParameterName.ToUpper() == InfoStoreFieldNames.ParentId
                               select filter;


            Int32 parentId = 0;
            //If no parentId Param is foudn then we used the root folder as parentId
            if (filterSearch.Count() == 0)
                parentId = this.RootParentId;
            else
            {
                //if we have sevral parent id, the filterparameters is not ok but we help by calling with root folder parent id
                BaseQueryParameter parentIdParam = filterSearch.SingleOrDefault();
                if (parentIdParam == null) parentId = this.RootParentId;
                else parentId = (Int32)parentIdParam.GetValue();
            }

            return this.GetObject(function, parentId, reccurse, nbLevels, 0);
        }



        /// <summary>
        /// Get a generic business object (InfoObject) from a path
        /// </summary>
        /// <param name="path"></param>
        /// <param name="isWildCard"></param>
        /// <returns></returns>
        public InfoObject GetObjectFromPath(String path, out Boolean isWildCard)
        {

            if (String.IsNullOrEmpty(path)) throw new ArgumentNullException("path");

            isWildCard = false;
            String[] pathNames = path.Split(new char[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);

            Int32 startFolderId = this.RootParentId;
            InfoObject foundObject = null;

            //Browse the path to find the last object
            for (Int32 i = 0; i < pathNames.Length; i++)
            {
                if (pathNames[i] == "*" && i == pathNames.Length - 1)
                {
                    isWildCard = true;
                    return foundObject;
                }

                InfoObject boeObject = this.GetObjectForPathProcessing(pathNames[i], startFolderId);
                if (boeObject == null)
                {
                    return null;
                }
                else
                {
                    startFolderId = boeObject.ID;
                    foundObject = boeObject;
                }
            }

            return foundObject;
        }

        /// <summary>
        /// Get a generic object by its name and its parent id. This method is only to be used with getobjectfrom path
        /// </summary>
        /// <param name="objectId"></param>
        /// <returns></returns>
        private InfoObject GetObjectForPathProcessing(String name, Int32 parentId)
        {

            InfoStoreQuery queryBuilder = new InfoStoreQuery(this.Query_Table);
            queryBuilder.AddParameter<String>(InfoStoreFieldNames.Name, OperatorType.Equal, name);
            queryBuilder.AddParameter<Int32>(InfoStoreFieldNames.ParentId, OperatorType.Equal, parentId);

            using (CrystalDecisions.Enterprise.InfoObjects results = _infoStore.Query(queryBuilder.GetQuery()))
            {
                if (results != null && results.Count > 0)
                {
                    return results[1];
                }
                else
                {
                    return null;
                }
            }
        }


        /// <summary>
        /// Get a new Object of the provider kind
        /// </summary>
        /// <returns></returns>
        public T GetNewObject()
        {
            CrystalDecisions.Enterprise.InfoObjects objects = _infoStore.NewInfoObjectCollection();
            return (T)objects.Add(this.ProgId);
        }

        /// <summary>
        /// Commit modification for an object
        /// </summary>
        /// <param name="objectBO"></param>
        public void Commit(CrystalDecisions.Enterprise.InfoObject objectBO)
        {
            _infoStore.Commit(objectBO.ParentInfoObjects);
        }

        /// <summary>
        /// Return the Progid of Business object based on the object Kind
        /// </summary>
        public String ProgId
        {
            get { return String.Format("CrystalEnterprise.{0}", this.Kind); }
        }



        public void AddFilterToId(OperatorType oper, Int32 id)
        {
            _queryBuilder.AddParameter<Int32>(InfoStoreFieldNames.Id, OperatorType.Equal, id);
        }

        public void AddFilterToName(OperatorType oper, String name)
        {
            _queryBuilder.AddParameter<String>(InfoStoreFieldNames.Name, oper, name);
        }

        public void AddFilterToParent(OperatorType oper, Int32 parentId)
        {
            _queryBuilder.AddParameter<Int32>(InfoStoreFieldNames.ParentId, oper, parentId);
        }

        public void AddFilterToKind()
        {
            _queryBuilder.AddParameter<String>(InfoStoreFieldNames.Kind, OperatorType.Equal, this.Kind.ToString());
        }

        public void AddFilterToCuid(String cuid)
        {
            _queryBuilder.AddParameter<String>(InfoStoreFieldNames.Cuid, OperatorType.Equal, cuid);
        }

        public void AddFilterToParentCuid(String cuid)
        {
            _queryBuilder.AddParameter<String>(InfoStoreFieldNames.ParentCuid, OperatorType.Equal, cuid);
        }


        public void AddFilterToCreationDate(OperatorType oper, DateTime creationDate)
        {
            _queryBuilder.AddParameter<DateTime>(InfoStoreFieldNames.CreationDate, oper, creationDate);
        }

        public void AddLimitOnTheQuery(Int32 limit)
        {
            _queryBuilder.AddLimit(limit);
        }

        /// <summary>
        /// The provider must define the kind of objects whihc will be retrieved
        /// </summary>
        public abstract ObjectKind Kind { get; }

        /// <summary>
        /// The provider must define the root object id
        /// </summary>
        public abstract Int32 RootParentId { get; }


        /// <summary>
        /// The provider must define the table nqme used in a query
        /// </summary>
        public abstract String Query_Table { get; }

    }

}
